package com.pointi.emoTools;

import java.util.ArrayList;

public class ezKMeans
{
	//___________________________________________________________________________

	private static final boolean isLog = false ;
	private static final double INFINITE = Math.pow(100, 100) ;
	private int NUM_CLUSTERS = 0 ;
	private int NUM_LOOPLIMIT = 999 ;
	private int dataSet_min_idx = 0 ;
	private int dataSet_max_idx = 0 ;
	
	public ezKMeans(int K, int LIMIT)
	{
		this.NUM_CLUSTERS = K ;
		this.NUM_LOOPLIMIT = LIMIT ;
	}
	
	public void fnSeparateData(ArrayList<double []> feature, ArrayList<ezData> result)
	{
		ArrayList<ezData> centroids = new ArrayList<ezData>() ;
		fnInitialize(feature, centroids) ; // [001]
		kMeanCluster(feature, result, centroids) ; // [002]
		if(isLog) {
			for (int i=0; i<NUM_CLUSTERS; i++) {
				System.out.println("Cluster " + i + " Includes : ") ;
				for (int j=0; j<result.size(); j++)
					if (result.get(j).cluster() == i) result.get(j).fnPrintElement() ;
				System.out.println() ;
			}
			System.out.println("Centroids Finalized at : ") ;
			for (int i=0; i<NUM_CLUSTERS; i++) {
				centroids.get(i).fnPrintCluster() ;
				centroids.get(i).fnPrintElement() ;
			}
			System.out.println() ;
		}
	}

	//___________________________________________________________________________
	// [001]

	private void fnGetInitialCentroids(ArrayList<double []> feature)
	{
		double dist = 0.0 ;
		double dist_min = ezKMeans.INFINITE ;
		double dist_max = 0.0 ;
		double [] zeroPoint = new double[feature.get(0).length] ;
		for (int i=0; i<zeroPoint.length; i++) { zeroPoint[i] = 0.0 ; }
		for (int i=0; i<feature.size(); i++) {
			dist = this.fnEuclidianDistance(feature.get(i), zeroPoint) ; // [003]
			if (dist < dist_min) { dist_min = dist ; dataSet_min_idx = i ; }
			if (dist > dist_max) { dist_max = dist ; dataSet_max_idx = i ; }
		}
	}
	private void fnInitialize(ArrayList<double []> feature, ArrayList<ezData> centroids)
	{
		fnGetInitialCentroids(feature) ;
		centroids.add(new ezData(feature.get(dataSet_min_idx), feature.get(0).length)) ;
		centroids.add(new ezData(feature.get(dataSet_max_idx), feature.get(0).length)) ;
		centroids.get(0).cluster(2) ;
		centroids.get(1).cluster(1) ;
		if(isLog) {
			System.out.println("Centroids Initialized at : ") ;
			centroids.get(0).fnPrintElement() ;
			centroids.get(1).fnPrintElement() ;
			System.out.println() ;
		}
		return ;
	}

	//___________________________________________________________________________
	// [002]

	private void kMeanCluster(ArrayList<double []> feature, ArrayList<ezData> dataSet, ArrayList<ezData> centroids)
	{
		int cluster = 0 ;
		int dataSet_idx = 0 ;
		int feature_depth = centroids.get(0).depth() ;

		double minimum = ezKMeans.INFINITE ; 
		double distance = 0.0 ;
		
		boolean isStillMoving = true ;

		ezData newData = null ;

		//// INITIAL LOOP
		while(dataSet.size() < feature.size()) 
		{
			newData = new ezData(feature.get(dataSet_idx), feature_depth) ;
			dataSet.add(newData) ;

			//// SET CLUSTER
			minimum = ezKMeans.INFINITE ;
			for (int i=0; i<NUM_CLUSTERS; i++) {
				distance = this.fnEuclidianDistance(newData, centroids.get(i)) ; // [003]
				if(isLog) System.out.println("CLUSTER : " + cluster + " DIST : " + distance) ;
				if (distance < minimum) {
					minimum = distance ;
					cluster = centroids.get(i).cluster() ;
					if(isLog) System.out.println("\tCLUSTER : " + cluster + " DIST_MIN : " + minimum) ;
				}
			}
			newData.cluster(cluster) ;

			//// NEW CENTROIDS
			for (int i=0; i<NUM_CLUSTERS; i++) {
				int member_cnt = 0 ;
				double [] element_sum = new double[feature_depth] ;
				double [] element_avg = new double[feature_depth] ;
				for (int z=0; z<feature_depth; z++) { element_sum[0] = 0.0 ; element_avg[0] = 0.0 ; }
				for (int j=0; j<dataSet.size(); j++) {
					if (dataSet.get(j).cluster() == i) {
						for (int p=0; p<feature_depth; p++)
							element_sum[p] += dataSet.get(j).element()[p] ;
						member_cnt++ ;
						if(isLog)
						{
							if(feature_depth == 13)
								System.out.println(dataSet_idx + " / total : " + element_sum[6] + " / " + element_sum[7] + " / " + element_sum[8] + " / " + element_sum[9] + " / totalCNT : " + member_cnt) ;
							else
								System.out.println(dataSet_idx + " / totalX : " + element_sum[0] + " / totalY : " + element_sum[1] + " / totalCNT : " + member_cnt) ;
						}
					}
				}
				if(member_cnt > 0) {
					for (int p=0; p<feature_depth; p++)
						element_avg[p] = element_sum[p] / (double)member_cnt ;
					centroids.get(i).element(element_avg) ;
					if(isLog) System.out.println("NC : " + centroids.get(0).element()[0] + ", " + centroids.get(0).element()[1]) ;
					if(isLog) System.out.println("NC : " + centroids.get(1).element()[0] + ", " + centroids.get(1).element()[1]) ;
				}
			}
			dataSet_idx++ ;
		}

		//// NORMAL LOOP
		int NUM_LOOPCNT = 0 ;
		while (isStillMoving) {
			if (NUM_LOOPCNT++ >= this.NUM_LOOPLIMIT) break ;
			for (int i=0; i<NUM_CLUSTERS; i++) {
				int member_cnt = 0 ;
				double [] element_sum = new double[feature_depth] ;
				double [] element_avg = new double[feature_depth] ;
				for (int z=0; z<feature_depth; z++) { element_sum[0] = 0.0 ; element_avg[0] = 0.0 ; }
				for (int j=0; j<dataSet.size(); j++) {
					if (dataSet.get(j).cluster() == i) {
						for (int p=0; p<feature_depth; p++)
							element_sum[p] += dataSet.get(j).element()[p] ;
						member_cnt++ ;
					}
				}
				if (member_cnt > 0) {
					for (int p=0; p<feature_depth; p++)
						element_avg[p] = element_sum[p] / (double)member_cnt ;
					centroids.get(i).element(element_avg) ;
				}
			}
			isStillMoving = false ;
			for (int i=0; i<dataSet.size(); i++) {
				ezData tData = dataSet.get(i) ;
				minimum = ezKMeans.INFINITE ;
				for (int j=0; j<NUM_CLUSTERS; j++) {
					distance = this.fnEuclidianDistance(tData, centroids.get(j)) ; // [003]
					if(distance < minimum){ minimum = distance ; cluster = centroids.get(j).cluster() ; }
				}
				tData.cluster(cluster) ;
				if (tData.cluster() != cluster) { tData.cluster(cluster) ; isStillMoving = true ; }
			}
		}
	}

	//___________________________________________________________________________
	// 003 // DIST

	private double fnEuclidianDistance(ezData d, ezData c)
	{
		double pow_sum = 0.0 ;
		for(int i=0; i<d.depth(); i++)
		{
			pow_sum += Math.pow((c.element()[i] - d.element()[i]), 2) ;
		}
		return Math.sqrt(pow_sum) ;
	}
	
	private double fnEuclidianDistance(double [] d, double [] c)
	{
		double pow_sum = 0.0 ;
		for(int i=0; i<d.length; i++)
		{
			pow_sum += Math.pow((c[i] - d[i]), 2) ;
		}
		return Math.sqrt(pow_sum) ;
	}

	//___________________________________________________________________________
	// EOC
}
